﻿using Shoy.Wcf.Behaviors;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

namespace Shoy.Wcf
{
    public class WcfHelper
    {
        private static ConcurrentDictionary<string, IClientChannel> _clientChannelCache;
        private const string CacheKey = "wcf:poolcache";

        private WcfHelper()
        {
            _clientChannelCache = new ConcurrentDictionary<string, IClientChannel>();
        }

        public static WcfHelper Instance
            => Singleton<WcfHelper>.Instance ?? (Singleton<WcfHelper>.Instance = new WcfHelper());

        private string WcfHost => ConfigurationManager.AppSettings.Get("wcfHost");

        private string TcpHost => ConfigurationManager.AppSettings.Get("tcpHost");

        private bool UseTcp => !string.IsNullOrWhiteSpace(TcpHost);

        private static string InterfaceCacheKey(Type interfaceType)
        {
            return $"{CacheKey}:{interfaceType.Name}";
        }

        private string ChannelCacheKey(Type interfaceType)
        {
            return $"{interfaceType.FullName}_{WcfHost}";
        }

        private static Binding InitBinding(bool useTcp = false)
        {
            if (useTcp)
            {
                return new NetTcpBinding
                {
                    Security =
                    {
                        Mode = SecurityMode.None,
                        Transport =
                        {
                            ClientCredentialType = TcpClientCredentialType.None
                        }
                    },
                    ReceiveTimeout = TimeSpan.FromMinutes(10),
                    SendTimeout = TimeSpan.FromMinutes(10),
                    OpenTimeout = TimeSpan.FromMinutes(5),
                    CloseTimeout = TimeSpan.FromMinutes(5)
                };
            }
            return new WSHttpBinding
            {
                Security =
                {
                    Mode = SecurityMode.None,
                    Transport =
                    {
                        ClientCredentialType = HttpClientCredentialType.None
                    }
                },
                ReceiveTimeout = TimeSpan.FromMinutes(10),
                SendTimeout = TimeSpan.FromMinutes(10),
                OpenTimeout = TimeSpan.FromMinutes(5),
                CloseTimeout = TimeSpan.FromMinutes(5)
            };
        }

        public ServiceHost OpenService(Type interfaceType, Type classType, Action<string> logAction = null)
        {
            var uri = new Uri(new Uri(WcfHost), interfaceType.Name);
            var binding = InitBinding(UseTcp);
            logAction = logAction ?? (msg => { });
            ServiceHost host;
            if (UseTcp)
            {
                var baseUri = new Uri(TcpHost);
                host = new ServiceHost(classType, baseUri);
                host.AddServiceEndpoint(interfaceType, binding, interfaceType.Name);
            }
            else
            {
                host = new ServiceHost(classType);
                host.AddServiceEndpoint(interfaceType, binding, uri);
            }

            if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
            {
                var behavior = new ServiceMetadataBehavior
                {
                    HttpGetEnabled = true,
                    HttpGetUrl = uri
                };
                host.Description.Behaviors.Add(behavior);
            }
            foreach (var endpoint in host.Description.Endpoints)
            {
                endpoint.Behaviors.Add(new LoggerEndpointBehavior(logAction));
            }
            host.Opened += delegate
            {
                var log = $"{uri} 服务已启动\t{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
                logAction(log);
            };
            host.Closed += delegate
            {
                var log = $"{uri} 服务已关闭\t{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
#if !DEBUG
                //移除
                //_redisClient.RemoveItemFromSet(InterfaceCacheKey(interfaceType), TcpHost + interfaceType.Name);
#endif
                logAction(log);
            };
            host.Faulted += delegate
            {
                logAction($"{uri} 服务状态为出错！\t{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
                //移除通道缓存
                var key = ChannelCacheKey(interfaceType);
                IClientChannel channel;
                _clientChannelCache.TryRemove(key, out channel);
            };
            //_redisClient.AddItemToSet(InterfaceCacheKey(interfaceType), TcpHost + interfaceType.Name);
            host.Open();
            return host;
        }

        /// <summary> 获取WCF服务Hosts </summary>
        /// <param name="interfaceType"></param>
        /// <returns></returns>
        private List<string> RemoteHosts(Type interfaceType)
        {
            var services = ConfigurationManager.AppSettings.Get("wcfServices");
            if (string.IsNullOrWhiteSpace(services))
                return new List<string>();
            return services.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();
            //// Redis 获取hosts
            //var redisKey = InterfaceCacheKey(interfaceType);
            //return (_redisClient.GetAllItemsFromSet(redisKey) ?? new HashSet<string>()).ToList();
        }

        /// <summary> 创建WCF通道 </summary>
        /// <typeparam name="TContract"></typeparam>
        /// <param name="host"></param>
        /// <param name="logAction"></param>
        /// <returns></returns>
        private IClientChannel CreateChannel<TContract>(string host, Action<string> logAction)
        {
            var key = ChannelCacheKey(typeof(TContract));
            var address = new Uri(new Uri(host), typeof(TContract).Name);
            IClientChannel channel;
            if (_clientChannelCache.ContainsKey(key))
            {
                if (_clientChannelCache.TryGetValue(key, out channel))
                {
                    //出错则移除
                    if (channel.State == CommunicationState.Faulted)
                    {
                        _clientChannelCache.TryRemove(key, out channel);
                    }
                    else
                    {
                        //没有打开则重新打开
                        if (channel.State != CommunicationState.Opened)
                        {
                            channel.Open();
                            logAction($"创建连接：{address}");
                        }
                        logAction($"当前服务：{address}");
                        return channel;
                    }
                }
            }
            var binding = InitBinding(host.StartsWith("net.tcp"));

            var factory = new ChannelFactory<TContract>(binding, new EndpointAddress(address));
            var client = factory.CreateChannel();

            channel = (IClientChannel)client;
            channel.Open();
            logAction($"创建连接：{address}");
            _clientChannelCache.TryAdd(key, channel);
            return channel;
        }


        /// <summary> 调用WCF服务 </summary>
        /// <typeparam name="TContract">需要调用的WCF契约</typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action">需要调用的WCF方法</param>
        /// <param name="logAction"></param>
        /// <param name="services"></param>
        public TResult Call<TContract, TResult>(Func<TContract, TResult> action, Action<string> logAction = null, params string[] services)
        {
            logAction = logAction ?? (msg => { });
            var interfaceType = typeof(TContract);
            var tryCount = 0;
            var hosts = (services.Length == 0 ? RemoteHosts(interfaceType) : services.ToList());
            while (tryCount < 3)
            {
                if (hosts == null || hosts.Count == 0)
                    break;
                var host = hosts[RandomHelper.Random().Next(0, hosts.Count)];
                try
                {
                    var channel = CreateChannel<TContract>(host, logAction);
                    return action((TContract)channel);
                }
                catch (EndpointNotFoundException)
                {
                    //无法连接
#if !DEBUG
                    //删除Redis
                    //_redisClient.RemoveItemFromSet(InterfaceCacheKey(interfaceType), host);
#endif
                    hosts.Remove(host);
                    tryCount++;
                }
                catch (CommunicationException)
                {
                    //通信异常
                    //套接字符超时等
                    //hosts.Remove(host);
                    //移除通道缓存
                    var key = ChannelCacheKey(interfaceType);
                    IClientChannel channel;
                    _clientChannelCache.TryRemove(key, out channel);
                    tryCount++;
                }
            }
            throw new Exception("没有可用的WCF服务");
        }

        /// <summary> 调用WCF服务 </summary>
        /// <typeparam name="TContract">需要调用的WCF契约</typeparam>
        /// <param name="action">需要调用的WCF方法</param>
        /// <param name="logAction"></param>
        /// <param name="services"></param>
        public void Call<TContract>(Action<TContract> action, Action<string> logAction = null, params string[] services)
        {
            Call<TContract, bool>(t =>
            {
                action(t);
                return true;
            }, logAction, services);
        }
    }
}
